home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 February: Tool Chest / Apple Developer CD Series Tool Chest February 1996 (Apple Computer)(1996).iso / Tool Chest / Development Tools & Languages / Macintosh Common Lisp Related / User Contributions / zebu v3.3.3 (LALR parser) / zebu-regex.lisp < prev    next >
Encoding:
Text File  |  1994-09-12  |  20.3 KB  |  531 lines  |  [TEXT/ttxt]

  1. ; -*- mode:     CL -*- ----------------------------------------------------- ;
  2. ; File:         zebu-regex.l
  3. ; Description:  A Lisp based Regular Expression Compiler
  4. ; Author:       Joachim H. Laubsch
  5. ; Created:      21-Sep-92
  6. ; Modified:     Mon Apr 18 13:38:26 1994 (Joachim H. Laubsch)
  7. ; Language:     CL
  8. ; Package:      ZEBU
  9. ; Status:       Experimental (Do Not Distribute) 
  10. ;
  11. ; (c) Copyright 1992, Hewlett-Packard Company
  12. ;;; All rights reserved.
  13. ;;;
  14. ;;; Use and copying of this software and preparation of derivative works
  15. ;;; based upon this software are permitted.  Any distribution of this
  16. ;;; software or derivative works must comply with all applicable United
  17. ;;; States export control laws.
  18. ;;; 
  19. ;;; This software is made available AS IS, and Hewlett-Packard Company
  20. ;;; makes no warranty about the software, its performance or its conformity
  21. ;;; to any specification.
  22. ;;; 
  23. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  24. ; Revisions:
  25. ; RCS $Log: $
  26. ; 13-Jan-93 (Joachim H. Laubsch)
  27. ;  Aletrnatives, to be indicated by \| need to be done!
  28. ;  7-Oct-92 (Joachim H. Laubsch)
  29. ;  made . fail on Newline in String
  30. ; 28-Sep-92 (Joachim H. Laubsch)
  31. ;  made ? work when it occured after a string (similar to the cases for +,*)
  32. ; 21-Sep-92 (Joachim H. Laubsch)
  33. ;  made behavior conform more with Emacs Lisp's STRING-MATCH
  34. ;  e.g. (string-match "\\(na\\)x\\1" "naxnana") matches now,
  35. ;  but before (string-match "(na)x\\1" "naxnana") did. 
  36. ;  "\(" is the grouping construct, and since \ is the quoting character,
  37. ;  it must be qoted as well, giving "\\(".
  38. ;  Avoided string-copying by introducing pointers in the match group case.
  39. ;
  40. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  41. ;;; -*- Mode:Common-Lisp; Package:ZEBU; Base:10 -*-
  42. ;;;
  43. ;;; This code was written by:
  44. ;;;
  45. ;;;    Lawrence E. Freil <lef@nscf.org>
  46. ;;;    National Science Center Foundation
  47. ;;;    Augusta, Georgia 30909
  48. ;;;
  49. ;;; If you modify this code, please comment your modifications
  50. ;;; clearly and inform the author of any improvements so they
  51. ;;; can be incorporated in future releases.
  52. ;;;
  53. ;;; nregex.lisp - My 4/8/92 attempt at a Lisp based regular expression
  54. ;;;               parser. 
  55. ;;;
  56. ;;;               This regular expression parser operates by taking a
  57. ;;;               regular expression and breaking it down into a list
  58. ;;;               consisting of lisp expressions and flags.  The list
  59. ;;;               of lisp expressions is then turned into a
  60. ;;;               lambda expression that can be later applied to a
  61. ;;;               string argument for parsing.
  62.  
  63.  
  64. (in-package "ZEBU")
  65. (provide "zebu-regex")
  66.  
  67. ;;;
  68. ;;; Declare the global variables for storing the paren index list.
  69. ;;;
  70. (declaim (special *regex-groups* *regex-groupings*))
  71.  
  72. ;; In Gnu Emacs Lisp's regular expressions the braces: {,} are not special,
  73. ;; neither are the parens: (,), nor the alternatives char: |
  74. ;;(defvar *regex-special-chars* "?*+.()[]\\${}")
  75. (defvar *regex-special-chars* "?*+.[]\\$")
  76.  
  77. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  78. ;;                                For debugging
  79. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  80. ;;;
  81. ;;; Declare some simple macros to make the code more readable.
  82. ;;;
  83. (defvar *regex-debug* nil)        ; Set to nil for no debugging code
  84.  
  85. (defmacro info (message &rest args)
  86.   (if *regex-debug*
  87.       `(format *standard-output* ,message ,@args)))
  88.  
  89. (eval-when (compile)
  90.   (setq *regex-debug* nil))
  91.  
  92. ;;;
  93. ;;; Declare a simple interface for testing.  You probably wouldn't want
  94. ;;; to use this interface unless you were just calling this once.
  95. ;;;
  96. #||
  97. (defun regex (expression string)
  98.   "Usage: (regex <expression> <string)
  99.    This function will call regex-compile on the expression and then apply
  100.    the string to the returned lambda list."
  101.   (let ((findit (cond ((stringp expression)
  102.                (regex-compile expression))
  103.               ((listp expression)
  104.                expression)))
  105.     (result nil))
  106.     (if (not (funcall (if (functionp findit)
  107.               findit
  108.             (eval `(function ,findit))) string))
  109.     (return-from regex nil))
  110.     (if (= *regex-groupings* 0)
  111.     (return-from regex t))
  112.     (dotimes (i *regex-groupings*)
  113.       (push (funcall 'subseq 
  114.              string 
  115.              (car (svref *regex-groups* i))
  116.              (cadr (svref *regex-groups* i)))
  117.         result))
  118.     (reverse result)))
  119.  
  120. ||#
  121.  
  122. ;; specialized to the :anchored T case
  123. ;; returns just the body of the fn with STRING, START, END free.
  124.  
  125. (defun regex-compile (source &aux (ln-source (length source)))
  126.   "Usage: (regex-compile <regular expression>)"
  127.   ;; If the expression was an empty string then it always
  128.   ;; matches (so lets leave early)
  129.   (when (= ln-source 0) (return-from regex-compile '(t)))
  130.   (macrolet ((add-exp (list)
  131.            ;; Add an item to the end of expression
  132.            `(setf expression-ln (+ expression-ln (length ,list))
  133.                       expression (append expression ,list)))
  134.          (add-exp1 (item)
  135.            `(setf expression-ln (1+ expression-ln)
  136.                       expression (nconc expression (list ,item)))))
  137.     
  138.     (info "Now entering regex-compile with ~S~%" source)
  139.     ;;
  140.     ;; This routine works in two parts.
  141.     ;; The first pass take the regular expression and produces a list of 
  142.     ;; operators and lisp expressions for the entire regular expression.  
  143.     ;; The second pass takes this list and produces the lambda expression.
  144.     (let ((expression            ; holder for expressions
  145.        ;;
  146.        ;; Generate the very first expression to save the starting index
  147.        ;; so that group 0 will be the entire string matched always
  148.        ;;
  149.        (list '(setf (svref *regex-groups* 0) (list index nil))))
  150.           (expression-ln 1)        ; length of expression
  151.       (group 1)            ; Current group index
  152.       (group-stack nil)        ; Stack of current group endings
  153.       (result nil)            ; holder for built expression.
  154.       )
  155.  
  156.       ;; If the first character is a literal, then do a quick scan to see
  157.       ;; if it is even in the string.
  158.       ;; If not then we can issue a quick nil, 
  159.       ;; otherwise we can start the search at the matching character to skip
  160.       ;; the checks of the non-matching characters anyway.
  161.       ;;
  162.       ;; If I really wanted to speed up this section of code it would be 
  163.       ;; easy to recognize the case of a fairly long multi-character literal
  164.       ;; and generate a Boyer-Moore search for the entire literal. 
  165.       ;;
  166.       ;; I generate the code to do a loop because on CMU Lisp this is about
  167.       ;; twice as fast a calling position.
  168.       ;;
  169.  
  170.       ;;
  171.       ;; Loop over each character in the regular expression building the
  172.       ;; expression list as we go.
  173.       ;;
  174.       (do ((eindex 0 (1+ eindex)))
  175.       ((= eindex ln-source))
  176.     (let ((current (char source eindex)))
  177.       (info "Now processing character ~A index = ~A~%" current eindex)
  178.       (case current
  179.         (#\.
  180.          ;;
  181.          ;; Generate code for a single wild character
  182.          ;;
  183.          (add-exp1 '(if (>= index length)
  184.              (return-from compare nil)
  185.              (incf index)))
  186.          )
  187.         (#\$
  188.          ;;
  189.          ;; If this is the last character of the expression then
  190.          ;; anchor the end of the expression, otherwise let it slide
  191.          ;; as a standard character (even though it should be quoted).
  192.          ;;
  193.          (if (= eindex (1- ln-source))
  194.          (add-exp1 '(if (/= index length)
  195.                  (return-from compare nil)))
  196.            (add-exp1 '(if (and (< index length)
  197.                    (eql (char string index) #\$))
  198.                (incf index)
  199.                (return-from compare nil)))))
  200.         (#\* (add-exp1 'ASTERIX))
  201.  
  202.         (#\+ (add-exp1 'PLUS))
  203.  
  204.         (#\? (add-exp1 'QUESTION))
  205.  
  206.         (#\[
  207.          ;;
  208.          ;; Start of a range operation.
  209.          ;; Generate a bit-vector that has one bit per possible character
  210.          ;; and then on each character or range, set the possible bits.
  211.          ;;
  212.          ;; If the first character is carat then invert the set.
  213.          (let* ((invert (eql (char source (1+ eindex)) #\^))
  214.             (bitstring (make-array
  215.                 256
  216.                 :element-type 'bit
  217.                 :initial-element (if invert 1 0)))
  218.             (set-char (if invert 0 1)))
  219.            (if invert (incf eindex))
  220.            (let (hi-char)
  221.          (do* ((x (1+ eindex) (1+ x))
  222.                (char (char source x)
  223.                  (if (= x ln-source)
  224.                  (error "No closing \"]\" found in ~a"
  225.                     source)
  226.                    (char source x))))
  227.               ((eql char #\]) (setf eindex x))
  228.            (info "Building range with character ~A~%" (char source x))
  229.            (if (let ((x+2 (+ x 2)))
  230.              (and (< x+2 ln-source)
  231.                   (eql (char source (1+ x)) #\-)
  232.                   (not (char= (setf hi-char (char source x+2))
  233.                       #\]))))
  234.                (progn
  235.              (if (char>= char hi-char)
  236.                  (error "Invalid range \"~A-~A\".  Ranges must be in acending order"
  237.                     char hi-char))
  238.              (do ((j (char-code char) (1+ j)))
  239.                  ((> j (char-code hi-char))
  240.                   (incf x 2))
  241.                (info "Setting bit for char ~A code ~A~%" (code-char j) j)
  242.                (setf (sbit bitstring j) set-char)))
  243.              (progn
  244.                ;;
  245.                ;; If the character is quoted then find out what
  246.                ;; it should have been
  247.                ;;
  248.                (when (char= char #\\)
  249.              (let (length)
  250.                (multiple-value-setq (char length)
  251.                  (regex-quoted (subseq source (1+ x)) invert))
  252.                (incf x length)))
  253.                (info "Setting bit for char ~C code ~A~%"
  254.                  char (char-code char))
  255.                (if (vectorp char)
  256.                (bit-ior bitstring char t)
  257.              (setf (sbit bitstring (char-code char))
  258.                    set-char))))))
  259.            (add-exp1 `(let ((range ,bitstring))
  260.                (if (>= index length)
  261.                    (return-from compare nil))
  262.                (if (= 1 (sbit range (char-code (char string index))))
  263.                    (incf index)
  264.                  (return-from compare nil))))))
  265.         (#\\
  266.          ;;
  267.          ;; Intrepret the next character as a special, range, octal, group or 
  268.          ;; just the character itself.
  269.          ;;
  270.          (multiple-value-bind (value length)
  271.          (regex-quoted (subseq source (1+ eindex)) nil)
  272.            (cond ((listp value) (add-exp value))
  273.              ((characterp value)
  274.               (case value
  275.             (#\(
  276.              ;;
  277.              ;; Start a grouping.
  278.              ;;
  279.              (incf group)
  280.              (push group group-stack)
  281.              (add-exp1 `(setf (svref *regex-groups* ,(1- group)) 
  282.                      (list index nil)))
  283.              (add-exp1 group))
  284.             (#\)
  285.              ;;
  286.              ;; End a grouping
  287.              ;;
  288.              (let ((group (pop group-stack)))
  289.                (add-exp1 `(setf (cadr (svref *regex-groups* ,(1- group)))
  290.                        index))
  291.                (add-exp1 (- group))))
  292.             (t (add-exp1 `(if (and (< index length)
  293.                        (eql (char string index) 
  294.                         ,value))
  295.                        (incf index)
  296.                        (return-from compare nil))))))
  297.              ((vectorp value)
  298.               (add-exp1 `(let ((range ,value))
  299.                   (if (>= index length)
  300.                       (return-from compare nil))
  301.                   (if (= 1 (sbit range (char-code (char string index))))
  302.                       (incf index)
  303.                     (return-from compare nil))))))
  304.            (incf eindex length)))
  305.         (t
  306.          ;;
  307.          ;; We have a literal character.  
  308.          ;; Scan to see how many we have and if it is more than one
  309.          ;; generate a string= verses as single eql.
  310.          ;;
  311.          (let* ((lit "")
  312.             (term (dotimes (litindex (- ln-source eindex) nil)
  313.                 (let ((litchar (char source (+ eindex litindex))))
  314.                   (if (position litchar *regex-special-chars*)
  315.                   (return litchar)
  316.                 (progn
  317.                   (info "Now adding ~A relative index ~A to lit~%"
  318.                     litchar litindex)
  319.                   (setf lit (concatenate 'string lit 
  320.                              (string litchar)))))))))
  321.            ;;(break "lit: ~S term: ~S" lit  term)
  322.            (if (= (length lit) 1)
  323.            (progn
  324.              (add-exp1 `(if (and (< index length)
  325.                      (eql (char string index)
  326.                       ,current))
  327.                  (incf index)
  328.                  (return-from compare nil))))
  329.          ;;
  330.          ;; If we have a multi-character literal then we must
  331.          ;; check to see if the next character (if there is one)
  332.          ;; is an asterix or a plus.  If so then we must not use this
  333.          ;; character in the big literal.
  334.          (progn
  335.            (when (member term '(#\* #\+ #\?))
  336.              (setf lit (subseq lit 0 (1- (length lit)))))
  337.            (if (= (length lit) 1)
  338.                (add-exp1 `(if (and (< index length)
  339.                        (eql (char string index)
  340.                     ,(schar lit 0)))
  341.                    (incf index)
  342.                    (return-from compare nil)))
  343.              (progn
  344.                (add-exp1 `(let ((new-index (+ index ,(length lit))))
  345.                    (if (< length new-index)
  346.                        (return-from compare nil))
  347.                    (if (string= string ,lit :start1 index
  348.                         :end1 new-index)
  349.                        (incf index ,(length lit))
  350.                      (return-from compare nil))))
  351.                (incf eindex (1- (length lit))))))))))))
  352.       ;;
  353.       ;; Plug end of list to return t.  If we made it this far then
  354.       ;; We have matched!
  355.       (add-exp1 '(setf (cadr (svref *regex-groups* 0)) index))
  356.       (add-exp1 '(return-from final-return t))
  357.       ;;
  358.       ;;
  359.       ;; Now take the expression list and turn it into a lambda expression
  360.       ;; replacing the special flags with lisp code.
  361.       ;; For example:  A BEGIN needs to be replaced by an expression that
  362.       ;; saves the current index, then evaluates everything till it gets to
  363.       ;; the END then save the new index if it didn't fail.
  364.       ;; On an ASTERIX I need to take the previous expression and wrap
  365.       ;; it in a do that will evaluate the expression till an error
  366.       ;; occurs and then another do that encompases the remainder of the
  367.       ;; regular expression and iterates decrementing the index by one
  368.       ;; of the matched expression sizes and then returns nil.  After
  369.       ;; the last expression insert a form that returns t so that
  370.       ;; if the entire nested sub-expression succeeds then the loop
  371.       ;; is broken manually.
  372.       ;; 
  373.       ;;
  374.       ;; Reversing the current expression makes building up the 
  375.       ;; lambda list easier due to the nesting of expressions when 
  376.       ;; an asterisk has been encountered.
  377.       (setf expression (reverse expression))
  378.       (info "~&Regular Expression:~%(~{~s~% ~}) ;; ~d"
  379.         expression expression-ln)
  380.  
  381.       (do ((elt 0 (1+ elt))) 
  382.           ((= elt expression-ln))
  383.     (let ((piece (nth elt expression))
  384.           (piece+1 (nth (1+ elt) expression)))
  385.       ;;
  386.       ;; Now check for PLUS, if so then ditto the expression and then let the
  387.       ;; ASTERIX below handle the rest.
  388.       ;;
  389.       ;; (princ ".")
  390.       (when (eql piece 'PLUS)
  391.         (cond ((listp piece+1) (push piece+1 result))
  392.           ;;
  393.           ;; duplicate the entire group
  394.           ;; NOTE: This hasn't been implemented yet!!
  395.           (t (warn "~%GROUP repeat hasn't been implemented yet~%"))))
  396.       (cond ((listp piece)        ; Just append the list
  397.          (push piece result))
  398.         ((eql piece 'QUESTION)    ; Wrap it in a block that won't fail
  399.          (cond ((listp piece+1)
  400.             (push `(progn (block compare ,piece+1)
  401.                 t)
  402.                   result)
  403.             (incf elt))
  404.                ;;
  405.                ;; This is a QUESTION on an entire group which
  406.                ;; hasn't been implemented yet!!!
  407.                ;;
  408.                (t
  409.             (warn "~%Optional groups not implemented yet~%"))))
  410.         ((or (eql piece 'ASTERIX) ; Do the wild thing!
  411.              (eql piece 'PLUS))
  412.          (when (listp piece+1)
  413.            ;;
  414.            ;; This is a single character wild card so
  415.            ;; do the simple form.
  416.            ;;
  417.            (setf result 
  418.              `((let ((oindex index))
  419.                  (block compare
  420.                    (do nil (nil) ,piece+1))
  421.                  (do ((start index (1- start)))
  422.                  ((< start oindex) nil)
  423.                    (let ((index start))
  424.                  (block compare
  425.                    ,@result))))))
  426.            (incf elt))))))    ; Just ignore everything else.
  427.  
  428.       (info "~&Result:~s" result)
  429.       ;;
  430.       ;; Now wrap the result in a lambda list that can then be 
  431.       ;; invoked or compiled, however the user wishes.
  432.       ;;
  433.       (setf result
  434.         `((setf *regex-groupings* ,group)
  435.           (block final-return
  436.         (block compare
  437.           (let ((index start)
  438.             (length end))
  439.             ,@result))))))))
  440.  
  441.  
  442. ;;;
  443. ;;; Define a function that will take a quoted character and return
  444. ;;; what the real character should be plus how much of the source
  445. ;;; string was used.  If the result is a set of characters, return an
  446. ;;; array of bits indicating which characters should be set.  If the
  447. ;;; expression is one of the sub-group matches, return a
  448. ;;; list-expression that will provide the match.  
  449. ;;;
  450.  
  451. (defun regex-quoted (char-string &optional (invert nil))
  452.   "Usage: (regex-quoted <char-string> &optional invert)
  453.        Returns either the quoted character or a simple bit vector of bits set for
  454.        the matching values"
  455.   (let ((first (char char-string 0))
  456.     (used-length 1)
  457.     result)
  458.     (setf result
  459.       (case first
  460.         (#\n #\NewLine)
  461.         (#\c #\Return)
  462.         (#\t #\Tab)
  463.         (#\d #*0000000000000000000000000000000000000000000000001111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
  464.         (#\D #*1111111111111111111111111111111111111111111111110000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111)
  465.         (#\w #*0000000000000000000000000000000000000000000000001111111111000000011111111111111111111111111000010111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
  466.         (#\W #*1111111111111111111111111111111111111111111111110000000000111111100000000000000000000000000111101000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111)
  467.         (#\b #*0000000001000000000000000000000011000000000010100000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
  468.         (#\B #*1111111110111111111111111111111100111111111101011111111111011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111)
  469.         (#\s #*0000000001100000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
  470.         (#\S #*1111111110011111111111111111111101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111)
  471.         (t (if (and (char>= first #\0) (char<= first #\9))
  472.            (if (and (> (length char-string) 2)
  473.                 (and (char>= (char char-string 1) #\0)
  474.                  (char<= (char char-string 1) #\9)
  475.                  (char>= (char char-string 2) #\0)
  476.                  (char<= (char char-string 2) #\9)))
  477.                ;;
  478.                ;; It is a single character specified in octal
  479.                ;;
  480.                (parse-integer char-string
  481.                       :end (setf used-length 3)
  482.                       :radix 8 :junk-allowed t)
  483.          
  484.              ;;
  485.              ;; We have a group number replacement.
  486.              ;;
  487.              (let ((group (- (char-code first) (char-code #\0))))
  488.                `((let* ((range (svref *regex-groups* ,group))
  489.                 (start-old (car (the cons range)))
  490.                 (end-old (cadr (the cons range)))
  491.                 (ln-nstring (- end-old start-old))
  492.                 (new-index (+ index ln-nstring)))
  493.                (if (< length new-index)
  494.                    (return-from compare nil))
  495.                (if (string= string string
  496.                     :start1 start-old
  497.                     :end1   end-old
  498.                     :start2 index
  499.                     :end2   new-index)
  500.                    (setq index new-index)
  501.                  (return-from compare nil)))))) 
  502.          first))))
  503.     (if (and (vectorp result) invert)
  504.     (bit-xor result #*1111111110011111111111111111111101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 t))
  505.     (values result used-length)))
  506.  
  507. #||
  508. (defun match-beginning (n)
  509.   (first (SVREF *REGEX-GROUPS* n)))
  510.  
  511. (defun match-end (n)
  512.   (second (SVREF *REGEX-GROUPS* n)))
  513. ||#
  514.  
  515. (defun def-regex-parser (name pattern)
  516.   (when (and (eql (symbol-package name) (find-package "LISP"))
  517.          (fboundp name))
  518.     (error "A lexical category should not name a Lisp function: ~s"
  519.        name))
  520.   (let* ((body (regex-compile pattern)))
  521.     `(defun ,name (STRING &optional (START 0) (END (length STRING)))
  522.       ,@(when *regex-debug*
  523.       '((info "~%Looking at: ~S..."
  524.          (subseq string START (min (+ 10 START) END)))))
  525.       (when (progn .,body)
  526.     (second (SVREF *REGEX-GROUPS* 0))))))
  527.  
  528. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  529. ;;                             End of zebu-regex.l
  530. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  531.